#!/bin/python
# ------------------------------------------------------------------------------
# This is a simple script to convert an OO.o project directory from using
# build.lst and makefile.mk's into Jamfiles.

import os
import stat
import sys
import re

# ------------------------------------------------------------------------------
#ONLY_MODULES = []
#SUMMARY = 1
ONLY_MODULES = ["sc"]
SUMMARY = 0

WARN_UNKNOWN = False

# ------------------------------------------------------------------------------
# List of makefile.mk details we can safely ignore

SAFE_IGNORE_INCLUDE = [ "scpre.mk", "settings.mk", "sc.mk", "target.mk",
                        "$(PRJ)$/inc$/swpre.mk", "$(PRJ)$/inc$/sw.mk" ]

SAFE_IGNORE_ASSIGNMENT = [ "PRJ",
                           # PCH handling (we do this elsewhere)
                           "PROJECTPCH4DLL", "PROJECTPCH", "PROJECTPCHSOURCE",
                           "PDBTARGET",
                           # Deprecated
                           "AUTOSEG",
                           # Dependency system
                           "CXXFILES"
                           ]

SAFE_IGNORE_MODULE = [ ".svn", "config_office", "default_images", "dmake",
                       "external_images", "ooo_custom_images", "solver", "external",
                       # FIXME(kaib):
                       # These modules actually have makefiles but they are all
                       # external and kludgy so we ignore them for now.
                       "beanshell", "berkeleydb", "bitstream_vera_fonts", "boost",
                       "curl", "epm", "expat", "freetype", "hsqldb", "icu", "jpeg",
                       "libwpd", "libxml2", "libxmlsec", "moz", "msfontextract",
                       "nas", "neon", "portaudio", "python", "qadevOOo",
                       "readlicense_oo", "rhino", "sablot", "smoketestoo_native",
                       "sndfile", "stlport", "zlib",
                       # We also ignore IDL generation for now
                       "offapi"
                       ]

# These are makefiles that don't seem to be depended on and thus linger around
SAFE_IGNORE_MAKEFILE = [ "svtools/mac/source/svdde/makefile.mk",
                         "svtools/mac/source/misc/makefile.mk",
                         "vcl/mac/source/app/makefile.mk",
                         "vcl/mac/source/gdi/makefile.mk",
                         "vcl/mac/source/window/makefile.mk",
                         "vcl/mac/source/src/makefile.mk"]

# Special targets are makefiles that are malformed and required hacks to
# get around
SPECIAL_TARGETS = {
    "binfilter/bf_sfx2/util/makefile.mk":"binfilter-bf_sfx",
    "chart2/com/sun/star/chart2/makefile.mk":"chart2-csschart2",
    "chart2/com/sun/star/layout/makefile.mk":"chart2-csslayout",
    "connectivity/qa/connectivity/tools/makefile.mk":"connectivity-tool",
    "desktop/macosx/source/makefile.mk":"desktop-osxbundle",
    "desktop/macosx/source/misc/makefile.mk":"dektop-macosx",
    "extensions/source/installation/office/protchk/makefile.mk":"extensions-opc",
    "filter/source/config/tools/utils/makefile.mk":"filter-utils",
    "filter/source/config/tools/merge/makefile.mk":"filter-merge",
    "filter/source/config/fragments/types/makefile.mk":"filter-fragments-types",
    "filter/source/config/fragments/filters/makefile.mk":"filter-fragments-filters",
    "filter/source/config/fragments/makefile.mk":"filter-fragments",
    "psprint/source/printer/makefile.mk":"psprint-printer",
    "psprint/source/fontmanager/makefile.mk":"psprint-fontmanager",
    "psprint/source/helper/makefile.mk":"psprint-helper",
    "rsc/source/rscpp/makefile.mk":"rsc-rscpp",
    "scripting/util/makefile.mk":"scripting-util",
    "sfx2/util/makefile.mk":"sfx2-util",
    "vcl/source/helper/makefile.mk":"vcl-helper",
    "xmerge/source/xmerge/makefile.mk":"xmerge-xmerge",
    "xmerge/source/bridge/makefile.mk":"xmerge-bridge",
    "xmerge/source/aportisdoc/makefile.mk":"xmerge-aportisdoc",
    "xmerge/source/pexcel/makefile.mk":"xmerge-pexcel",
    "xmerge/source/pocketword/makefile.mk":"xmerge-pocketword",
    "xmerge/source/htmlsoff/makefile.mk":"xmerge-htmlsoff",
    "xmerge/util/makefile.mk":"xmerge-util"
    }

# These are regexps for targets to be procecessed later
POSTPROCESS_TARGETS = [
    # standard library
    "LIB[1-9]TARGET", "LIB[1-9]OBJFILES", "LIB[1-9]FILES",
    # SRS translation file
    "SRS[1-9]NAME", "SRC[1-9]FILES"
    ]

JAMFILE_AUTOGEN_MARKER = "MK2JAM_AUTOGEN_MARKER"

# ------------------------------------------------------------------------------
# Warning and completion statistics

def print_warning(makefile_name, warning_txt):
    global processed_files, warning_files, warning_lines
    if not makefile_name in warning_files:
        print makefile_name + ":0: Error: File contains warnings"
    warning_files[makefile_name] = 1
    warning_lines += 1
    if not SUMMARY == 1:
        print "Warning: " + warning_txt

def print_error(makefile_name, error_txt):
    if SUMMARY == 1:
        print makefile_name + ":0: Error: " + error_txt
    else:
        sys.exit(makefile_name + ":0: Error: " + error_txt)

# ------------------------------------------------------------------------------
# this function encapsulates special case include handling.

def specialCaseInclude(module, target, include, makefile_name):
    if target == "":        
        print_error(makefile_name, "Target not defined in specialCaseInclude")
    if module == "sc":
        if include == "$(PRJ)$/util$/makefile.pmk":
            return "CXXFLAGS on " + target + " += $(CXXFLAGS_VISIBILITY) ;\n"
    return ""

# ------------------------------------------------------------------------------
# Removes makefile whitespace and comments

# FIXME trailing \ should be ok if followed by empty line ..
def stripMKWhitespace(makedata):
    lines = makedata.splitlines()
    collapsed = []
    compoundline = ""
    for line in lines:
        line = line.strip()
        if len(line) > 1 and line[-1] == "\\":
            compoundline += line[0:-1].strip() + " "
        elif len(line) == 0 or line[0] == "#":
            if not compoundline == "":
                collapsed.append(compoundline)
                compoundline = ""
            continue
        else:
            collapsed.append(compoundline + line)
            compoundline = ""
    if compoundline:
        collapsed.append(compoundline)
    return collapsed

# ------------------------------------------------------------------------------
# Processing and output functions

def stripDollarPath(filelist):
    result = []
    for objfile in filelist:
        result.append(objfile.replace("$/", " "))
    return result
    

# Strip the SLO definitions and turn OBJ into CXX
def cxxFromObj(filelist):
    result = []
    for objfile in filelist:
        objfile = objfile.strip()
        result.append(objfile[objfile.rfind("/") + 1:].replace(".obj", ".cxx"))
    return result

# --------------------------------------------------------------------------------
# Utility functions

# Check if a filelist could be replaced by a glob expression
def globifyFileList(filelist, directory):
    suffix = filelist[0][filelist[0].rfind("."):]
    allFiles = os.listdir(directory)
    ignoredFiles = []
    for iFile in allFiles:
        if re.match(".*" + re.escape(suffix), iFile):
            ignoredFiles.append(iFile)
    
    for iFile in filelist:
        if not iFile in ignoredFiles:
            print_warning(directory + "/makefile.mk", "File " + iFile + " not present in directory " + directory)
            return filelist
        ignoredFiles.remove(iFile)
    globExpression = ""
    if len(ignoredFiles) > 0:
        globExpression = "[ Glob $(SEARCH_SOURCE) : *" + suffix + " : " + \
                         " ".join(ignoredFiles) + " ]"
    else:
        globExpression = "[ Glob $(SEARCH_SOURCE) : *" + suffix + " ]"
    if len(globExpression) < len(" ".join(filelist))/2:
        return [globExpression]
    else:
        return filelist

def isJamfileManual(filename):
    fJamfile = file(filename, "r+");
    data = fJamfile.read()
    fJamfile.close()
    return data.find(JAMFILE_AUTOGEN_MARKER) == -1

# --------------------------------------------------------------------------------
# Jam output functions

def jamAssignment(variable, assignment, valueList):
    if len(valueList) > 1:
        return variable + " " + assignment + "\n  " + "\n  ".join(valueList) + "\n  ;\n"
    else:
        return variable + " " + assignment + " " + " ".join(valueList) + " ;\n"        

def jamFuncall(function, parameterList):
    if len(parameterList) > 3:
        return function + " " + " : \n  ".join(parameterList) + " ;\n"
    else:
        return function + " " + " : ".join(parameterList) + " ;\n"

def jamIndent(expression, indent):
    lines = expression.splitlines()
    lineindent = "\n" + indent
    return indent + lineindent.join(lines) + "\n"


# ------------------------------------------------------------------------------
# Python function that converts a makefile from a single directory to a Jamfile

def mk2jam(subdir_paths):
    subdir = "/".join(subdir_paths)
    if(isJamfileManual(subdir + "/Jamfile")): return
    makefile_name = subdir + "/makefile.mk"
    fMakefile = file(makefile_name, 'r')
    mksource = stripMKWhitespace(fMakefile.read())
    fMakefile.close()

    jamData = "# This file was generated by mk2jam.py\n"
    jamData += "# Remove this line to disable automatic generation: " + JAMFILE_AUTOGEN_MARKER + "\n\n"
    jamData += "SubDir TOP " + " ".join(makefile_name.split("/")[:-1]) + " ;\n\n"
    jamData += "# All variables assigned in local scope to avoid clashes\n\n{\n\n"
    indent = ""
    target = ""
    mkVars = {}
    mkVars["target_special"] = 0
    postprocess_re = re.compile("(" + ")|(".join(POSTPROCESS_TARGETS) + ")")
    # Special case targets for malformed makefiles
    if makefile_name in SPECIAL_TARGETS:
        target = SPECIAL_TARGETS[makefile_name]
        mkVars["target_special"] = 1

    # --------------------------------------------------
    # Main loop
    for line in mksource:
        # Handle dmake control statements
        if line[0] == ".":
            if line.startswith(".INCLUDE"):
                include = line.split(":")[1].strip()
                special = specialCaseInclude(subdir_paths[0], target, include, makefile_name)
                if special == "" and not include in SAFE_IGNORE_INCLUDE:
                    print_warning(makefile_name, "Ignored include " + include)
            elif line.startswith(".IF"):
                jamData += indent + "\nif " + line[3:].strip().replace("==", "=") + " {\n"
                indent += "  "
            elif line.startswith(".ELSE"):
                jamData += indent[:-2] + "}\n" + indent[:-2] + "else {\n"
            elif line.startswith(".ENDIF"):
                indent = indent[:-2]
                jamData += indent + "}\n"

        # ------------------------------------------------------------
        # Variable assignment
        elif len(line.split("=")) > 1:
            splitline = line.split("=")
            keyword = splitline[0].strip()
            assignment = "="
            args = splitline[1].split()

            # Handle special case assignment operators
            if splitline[0][-1] == "+":
                keyword = keyword[:-1].strip()
                assignment = "+="
            elif splitline[0][-1] == ":":
                keyword = keyword[:-1].strip()
                
            # ------------------------------------------------------------
            # Handle special case assignment
            if mkVars["target_special"] == 0 and keyword == "PRJNAME":
                if not target == "":
                    print_error(makefile_name, "target already assigned to")                    
                target = args[0]
            elif mkVars["target_special"] == 0 and keyword == "TARGET":
                if target == "":
                    print_error(makefile_name, "PRJNAME not assigned before target")
                target = target + "-" + args[0]
                jamData += "local TARGET = " + args[0] + " ;\n\n"
            #            print "keyword: " + keyword + "\nargs: " + " ".join(args)

            # ------------------------------------------------------------
            # Cxx Compilation
            elif keyword == "SLOFILES":
                jamData += jamIndent(jamAssignment("CXXFILES on " + target, assignment,
                                                   globifyFileList(cxxFromObj(args), subdir)),
                                     indent)
                mkVars["SLOFILES"] = 1
            elif keyword == "EXCEPTIONSFILES":
                jamData += jamIndent(jamAssignment("EXCEPTIONSFILES on " + target, assignment,
                                                   globifyFileList(cxxFromObj(args), subdir)),
                                     indent)
            elif keyword == "NOOPTFILES":
                jamData += jamIndent(jamAssignment("NOOPTFILES on " + target, assignment,
                                                   globifyFileList(cxxFromObj(args), subdir)),
                                     indent)
            elif keyword == "EXCEPTIONSNOOPTFILES":
                jamData += jamIndent(jamAssignment("EXCEPTIONSNOOPTFILES on " + target, assignment,
                                                   globifyFileList(cxxFromObj(args), subdir)),
                                     indent)
            elif keyword == "ENABLE_EXCEPTIONS":
                if target == "": print_error(makefile_name, "Trying to use target before assignment")
                jamData += "\nCXXFLAGS on " + target + " += [ FDefines EXCEPTIONS_ON ] $(CXXFLAGS_EXCEPTIONS) ;\n"
            elif keyword == "LIBTARGET":
                mkVars["LIBTARGET"] = args[0].strip().lower
            elif postprocess_re.match(keyword):
                mkVars[keyword] = " ".join(args)
            elif keyword in SAFE_IGNORE_ASSIGNMENT:
                continue
            else:
                jamData += "# " + line + "\n"
                print_warning(makefile_name, "Ignored assignment keyword " + keyword)
        else:
            # We are ignoring unknown lines
            jamData += "# " + line + "\n"
            if WARN_UNKNOWN:
                print_warning(makefile_name, "Ignoring unknown line: " + line)

    # ----------------------------------------------------------------------
    # Now we add implicit targets for compilation, linkink, etc.
    target_handle = target.split("-")
    if "SLOFILES" in mkVars:
        jamData += jamIndent(jamFuncall("CxxSourceDirCompile",  [" ".join(target_handle)]), indent)
        if not "LIBTARGET" in mkVars or not mkVars["LIBTARGET"] == "no":
            jamData += jamIndent(jamFuncall("CxxSourceDirLib", [" ".join(target_handle), target_handle[-1] ]), indent)

    # ----------------------------------------------------------------------
    # These are unrolled targets that require us to loop through the set
    for iNum in xrange(9):
        libName = "LIB" + str(iNum) + "TARGET"
        srsName = "SRS" + str(iNum) + "NAME"
        if libName in mkVars:
            libObjs = "LIB" + str(iNum) + "OBJFILES"
            libFiles = "LIB" + str(iNum) + "FILES"
            libSources = ""
            if libObjs in mkVars:
                libSources += mkVars[libObjs]
            if libFiles in mkVars:
                libSources += mkVars[libFiles]
            if libSources == "":
                print_error(makefile_name, "LIB" + str(iNum) + "TARGET without any source files") 
            jamData += jamFuncall("CxxTargetLib", [" ".join(target_handle),
                                                   mkVars[libName].replace("$(TARGET)", target_handle[-1]).replace("$/", "$(/)"),
                                                   libSources.replace("$/", "$(/)")])
        if srsName in mkVars:
            srcFiles = "SRC" + str(iNum) + "FILES"
            if srcFiles not in mkVars:
                print_error(makefile_name, "Unable for find corresponding " + srcFiles)
            jamData += jamFuncall("SRSFile", [" ".join(target_handle),
                                         "[ FModuleBuildDir " + target_handle[0] + " : srs " +
                                         mkVars[srsName].replace("$(TARGET)", target_handle[-1]) + ".srs ]",
                                         mkVars[srcFiles],
                                         "localize.sdf"])
    
    # ------------------------------------------------------------
    # Close local scope
    jamData += "\n}\n\n"
    # ------------------------------------------------------------
    # Write out the Jamfile
    # FIXME(kaib): Add control to change Jamfiles into manual update
    jamfile = file(subdir + "/Jamfile", 'w')
    jamfile.write(jamData)
    jamfile.close()

# ------------------------------------------------------------------------------
# Convert a single module

if __name__ == "__main__":
    global processed_files, warning_files, warning_lines
    processed_files = 0
    warning_files = {}
    warning_lines = 0

    dirs = os.listdir(".")
    for module in dirs:
        if not stat.S_ISDIR(os.stat(module).st_mode) or module in SAFE_IGNORE_MODULE: continue
        if len(ONLY_MODULES) > 0 and not module in ONLY_MODULES: continue
        build_lst = file(module + "/prj/build.lst").read().splitlines()
        target_jamfiles = ""
        
        # Loop through the build file searching out makefile.mk's
        for buildline in build_lst:
            buildline = buildline.strip()
            if len(buildline) < 1 or buildline[0] == "#" or buildline.find("nmake") == -1: continue
            subdir = buildline.split()[1].split("\\")
            if len(subdir) == 1:
                print_error(makefile, "Conflict with root Jamfile: " + subdir[0])
                exit


            # These are buggy makefiles lingering in prj/build.lst files
            makefile = "/".join(subdir) + "/makefile.mk"
            if makefile in SAFE_IGNORE_MAKEFILE: continue

            mk2jam(subdir)
            target_jamfiles += "SubInclude TOP " + " ".join(subdir) + " ;\n"
            processed_files += 1

        # Write the module level Jamfile
        mod_jam_name = module + "/Jamfile"
        if os.access(mod_jam_name, os.F_OK):
            mod_jamfile = file(mod_jam_name, 'r')
            prevcontent = mod_jamfile.read().split("#<<<AUTOGEN-MARKER>>>\n")
            mod_jamfile.close()
            mod_jamfile = file(mod_jam_name, 'w')
            mod_jamfile.write(prevcontent[0] + "#<<<AUTOGEN-MARKER>>>\n" + target_jamfiles +
                              "#<<<AUTOGEN-MARKER>>>\n")
            if len(prevcontent) > 2:
                mod_jamfile.write(prevcontent[2])
            mod_jamfile.close()
        else:
            mod_jamfile = file(mod_jam_name, 'w')
            mod_jamfile.write("#<<<AUTOGEN-MARKER>>>\n" + target_jamfiles +
                              "#<<<AUTOGEN-MARKER>>>\n")
            mod_jamfile.close()
    success_files = processed_files - len(warning_files)
    success_percentage = success_files * 100.0 / processed_files
    print "Completed %.1f%% (%d/%d) %d lines of warnings" % (success_percentage, success_files, processed_files, warning_lines)


#    makefile = file("makefile.mk", 'r')
#    source = collapse(makefile)
#    result = convert(source, "sc-core-data")
#    print result
